"""
交换机定义为网络
"""
import traceback
import copy

import IPy
from ipaddress import ip_network, ip_address
from flask import current_app
from flask_restx import abort, reqparse
from openstack import exceptions

from app.api.v1.webhook.webhook_manager import WebHookManager
from tools.basic_tools import Tools
from app.api.v1.organizer.rangeVm import VmBase
from app.common.range_manager import RangeManager
from app.models import rangeModel, switchModel
from configs.settings import flask_config
from tools import db, redis_conn
from collections import  OrderedDict
from app.common.transaction_manager import create_transaction


class SwitchNetwork(VmBase):

    def post(self):
        parser = reqparse.RequestParser()
        parser.add_argument("range_uuid", type=str, help="UUID不能为空", required=True)
        parser.add_argument("cidr", type=str, required=True)
        parser.add_argument("subnet_id", type=str)
        parser.add_argument("switch_name", type=str, default="交换机")
        args = parser.parse_args()
        switch_name = args["switch_name"]
        #  校验网络信息
        range_info = rangeModel.RangeLinkModule.query.filter_by(range_uuid=args.range_uuid).first()
        if not range_info:
            abort(500, message="场景参数错误, 请重新输入")
        # 记录入库
        try:
            # 如果传入subnet_id
            if not args.subnet_id:
                switch_obj = switchModel.Switchs.query.filter_by(
                    range_uuid=args.range_uuid, cidr=args.cidr
                ).first()
                if switch_obj and switch_obj.subnet_id:
                    abort(500, message=f'该网段({args["cidr"]})以存在请重新选择网段')
                subnet_id=Tools.get_uuid4_no_line()
                obj = switchModel.Switchs(
                    range_uuid=args.range_uuid,
                    subnet_id=subnet_id,
                    cidr=args.cidr,
                    network_id=range_info.network_id,
                    switch_name=switch_name
                )
                db.session.add(obj)
                db.session.commit()
                switch_id = obj.id
            else:
                subnet_id = args.subnet_id
                switch_obj = switchModel.Switchs.query.filter_by(subnet_id=subnet_id).first()
                switch_id = switch_obj.id
                # 更新 network_id
                switchModel.Switchs.query.filter_by(subnet_id=subnet_id).update({"network_id": range_info.network_id})
                db.session.commit()
            
            # 构建参数
            action_param = OrderedDict()
            action_param["create_l3_network"] =  {
                    "l2_network_uuid": range_info.network_id, 
                    "name": switch_name, 
                    "resource_uuid": subnet_id
                }
            action_param["attach_network_service_to_l3Network"] = {
                    "l3_network_uuid": subnet_id,
                    "network_service": current_app.config["ZSTACK_CLOUD"]["network_service"],
            }
            
            action_param["create_l3_network_ip_ranges"] = {
                    "l3_network_uuid": subnet_id, 
                    "network_cidr": args.cidr,
            }
            action_param["add_dns_to_l3_network"] = {
                "l3_network_id": subnet_id,
                "dns": current_app.config["ZSTACK_CLOUD"]["dns"],
                "state": 1
            }
            # 查看关联的路由器是否已经创建, 创建则挂载
            routes = db.session.query(switchModel.Routes).filter(switchModel.Routes.binding_switchs.like(f'%{switch_id}%'), switchModel.Routes.state == 1).all()
            for route in routes:
                port_obj = switchModel.Ports.query.filter_by(device_id=route.op_route, range_uuid=args.range_uuid).first()
                port_id = port_obj.port_id
                ip = port_obj.ip
                action_param["attach_l3network_route"] = {"l3_network_uuid": subnet_id, "route_uuid": route.op_route, "state": 1}
                action_param["update_vm_ip_database"] = {"port_id": port_id, "ip": ip, "device_type": "router", "l3_network_uuid": subnet_id, "network_id": range_info.network_id}
            
            create_transaction(action_param, args.range_uuid)
        except Exception as e:
            db.session().rollback()
            current_app.logger.exception(e)
            abort(500, message="交换机创建失败")

        return {"code": 200,  "data": {"subnet_id": subnet_id, "switch_id": switch_id}}
    
    def get(self, range_uuid, subnet_id):

        if range_uuid:
            if subnet_id == "no":
                switchs = switchModel.Switchs.query.filter_by(
                    range_uuid=range_uuid
                ).all()
                cidr = []
                all_cidr = flask_config.NETWORK["CIDRS"]
                for switch in switchs:
                    cidr.append(switch.cidr)
                no_use_cidr = list(set(all_cidr) - set(cidr))
                info = {"use_cidr": cidr, "no_use_cidr": no_use_cidr}
                return {"code": 200, "data": info}
            else:
                switch = switchModel.Switchs.query.filter_by(
                    range_uuid=range_uuid, subnet_id=subnet_id
                ).first()
                info = {}
                if switch:
                    info["swich_id"] = switch.id
                    info["cidr"] = switch.cidr
                    info["subnet_id"] = switch.subnet_id
                return {"code": 200, "data": info}
        else:
            abort(400, message="靶场id不可为空")

    def delete(self):
        parser = reqparse.RequestParser()
        parser.add_argument("range_uuid", help="靶场信息有误", required=True)
        parser.add_argument("subnet_id", help="靶场信息有误", required=True)
        args = parser.parse_args()

        if not all([args["range_uuid"], args["subnet_id"]]):
            abort(400, message="参数错误")
        else:
            # 判断交换机绑定的虚拟机
            vm_list = rangeModel.RangeVm.query.filter_by(subnet_id=args.subnet_id).all()
            if vm_list:
                abort(400, message="该交换机有连接的虚拟机，不可直接删除，请先解绑！！！")
            # 请求云服务
            action_param = OrderedDict()
            action_param["del_l3_network"] = {"l3_network_uuid": args.subnet_id, "state": 1}
            action_param["del_switch_database"] = {"subnet_id": args.subnet_id}
            create_transaction(action_param, args.range_uuid)
            return {"code": 200, "data": "删除成功"}


class SubnetPort(VmBase):
    def post(self):
        # number、ip不可同时存在
        parser = reqparse.RequestParser()
        parser.add_argument("range_uuid", help="靶场信息有误", required=True)
        parser.add_argument("subnet_id", help="交换机信息有误", required=True)
        parser.add_argument("number")
        parser.add_argument("ip")
        args = parser.parse_args()

        number = args.get("number")
        if not number:
            number = "1"
        ip = args.get("ip")
        # number、ip不可同时存在
        if int(number) > 1:
            if ip:
                abort(400, message="创建多个端口时，不能指定ip")
        # 去掉dhcp，gw
        if ip and ip.split(".")[-1] in ["1", "2"]:
            abort(400, message="该ip地址不可使用")

        switch = switchModel.Switchs.query.filter_by(
            range_uuid=args["range_uuid"], subnet_id=args["subnet_id"]
        ).first()
        if switch:
            try:
                ips = list(IPy.IP(switch.cidr))
                ips.pop(0)
                ips.pop(-1)
                if ip and (IPy.IP(ip) not in ips):
                    abort(400, message="指定ip地址不在该交换机的网段内")
            except ValueError:  # noqa
                abort(400, message="IP不合法")
            port_list = []
            for _ in range(int(number)):
                try:
                    if not ip:
                        port = self.conn.create_port(
                            switch.network_id,
                            fixed_ips=[{"subnet_id": switch.subnet_id}],
                        )
                    else:
                        port = self.conn.create_port(
                            switch.network_id,
                            fixed_ips=[
                                {"ip_address": ip, "subnet_id": switch.subnet_id}
                            ],
                        )

                except exceptions.ConflictException as e:
                    current_app.logger.exception(e)
                    abort(400, message="指定ip地址已存在")

                port_info = {
                    "port_id": port.id,
                    "ip": port["fixed_ips"][0]["ip_address"],
                }
                port_list.append(port_info)

                # 记录未绑定的port，以供开机时使用
                port = switchModel.Ports(
                    port_id=port.id,
                    range_uuid=args["range_uuid"],
                    ip=port.fixed_ips[0]["ip_address"],
                    network_id=switch.network_id,
                    subnet_id=switch.subnet_id,
                )
                switchModel.db.session.add(port)
                switchModel.db.session.flush()

            return {"code": 200, "data": port_list}


class VmPortBinding(VmBase):
    def get(self, range_uuid, vm_uuid, subnet_id):
        # 锁防连续多次触发
        with redis_conn.lock(
            f"ORDER_VM_PORT_BINDING_CHECK::{range_uuid}::{vm_uuid}::{subnet_id}",
            timeout=20,
        ):
            # FIXME 可能需要更好的线程内刷新flask-sqlalchemy缓存方式，
            #  事务和信号无法并发时无法处理，expire、refresh无效
            db.session.close()
            return self.vm_port_bind(range_uuid, vm_uuid, subnet_id)

    def check_or_create_port(self, subnet_id, vm_info, switch):
        """检查端口，已创建返回端口信息，未创建则创建后返回端口信息"""
        port = switchModel.Ports.query.filter_by(
            subnet_id=subnet_id, ip=vm_info.v4_fixed_ip
        ).first()
        if port:
            return {
                "code": 200,
                "data": [{"port_id": port.port_id, "ip": vm_info.v4_fixed_ip}],
            }
        try:
            port_op = self.conn.create_port(
                switch.network_id,
                fixed_ips=[
                    {
                        "ip_address": vm_info.v4_fixed_ip,
                        "subnet_id": switch.subnet_id,
                    }
                ],
            )
        except exceptions.SDKException as e:
            abort(400, message=e)
        else:
            try:
                port_obj = switchModel.Ports(
                    port_id=port_op.id,
                    range_uuid=vm_info.range_uuid,
                    ip=port_op.fixed_ips[0]["ip_address"],
                    network_id=switch.network_id,
                    subnet_id=switch.subnet_id,
                )
                db.session.add(port_obj)
                db.session.commit()
            except Exception as e:
                current_app.logger.error("交换机端口创建成功，数据库保存失败", exc_info=e)
                db.session.rollback()
                self.conn.delete_port(port_op.id)
                abort(400, message="交换机端口创建失败")
            else:
                return {
                    "code": 200,
                    "data": [
                        {"port_id": port_obj.port_id, "ip": vm_info.v4_fixed_ip}
                    ],
                }

    def vm_port_bind(self, range_uuid, vm_uuid, subnet_id):
        if subnet_id == "null":
            return {"code": 200, "data": []}

        switch = switchModel.Switchs.query.filter_by(
            range_uuid=range_uuid, subnet_id=subnet_id
        ).first()
        if not switch:
            abort(400, message="交换机未创建")

        range_manager = RangeManager(range_uuid=range_uuid)
        vm_info = range_manager.get_vm_from_this(vm_uuid=vm_uuid)

        if vm_info.subnet_id != switch.subnet_id:
            abort(400, message="交换机不是该虚拟机相连的交换机")

        # 获取端口
        ports = switchModel.Ports.query.filter_by(range_uuid=range_uuid, subnet_id=subnet_id, device_id=vm_uuid).all()

        port_list = [{"ip": port.ip, "port_id": port.port_id} for port in ports]
        # 入口ip
        if vm_info.external_ip:
            port_list.append({
                "ip": vm_info.external_ip
            })
        return {"code": 200, "data": port_list}


class Router(VmBase):
    
    def delete_route(self, range_uuid, router_id):
        if not all([range_uuid, router_id]):
            abort(400, message="参数错误")
        router = switchModel.Routes.query.filter_by(op_route=router_id).first()
        if not router:
            abort(400, message="该路由器不存在")
        print(router.binding_switchs)
        if router.binding_switchs:
            abort(400, message="该路由器存在绑定的交换机，请先解除绑定")
        action_param = OrderedDict()
        action_param["del_vm_destroy"] = {"vm_uuid": router_id, "state": 1}
        action_param["del_router_database"] = {"router_uuid": router_id}
        create_transaction(action_param, range_uuid)   


    def post(self):
        parser = reqparse.RequestParser()
        parser.add_argument("range_uuid", help="靶场信息有误", required=True)
        parser.add_argument("router_name", default="路由器")
        parser.add_argument("router_id", help="路由器信息错误")
        args = parser.parse_args()

        range_uuid = args["range_uuid"]
        router_id = args["router_id"]
        router_name = f"{range_uuid[:5]}-{args['router_name']}"
        try:
            action_param = OrderedDict()
            action_param["create_vpc_router"] = {}
            if  not router_id:
                obj = switchModel.Routes(op_route=Tools.get_uuid4_no_line(), range_uuid=range_uuid)
                op_route = obj.op_route
                db.session.add(obj)
                db.session.commit()
            else:
                router_obj = switchModel.Routes.query.filter_by(op_route=router_id, range_uuid=range_uuid).first()
                if router_obj.state != 0:
                    return {"code": 200, "data": {"router_id": obj.op_route}}
                # 创建并绑定交换机
                op_route = router_id
                # 获取交换机信息
                for switch_id in router_obj.binding_switchs.split(","):
                    swicth_obj = switchModel.Switchs.query.filter_by(id=switch_id, range_uuid=range_uuid).first()
                    # 查看交换机是否创建
                    if not swicth_obj.network_id:
                        continue
                    port_obj = switchModel.Ports.query.filter_by(device_id=router_id, range_uuid=range_uuid).first()
                    l3_network_uuid = swicth_obj.subnet_id
                    port_id = port_obj.port_id
                    ip = port_obj.ip
                    action_param["attach_l3network_route"] = {"l3_network_uuid": l3_network_uuid, "route_uuid": router_id}
                    action_param["update_vm_ip_database"] = {"port_id": port_id, "ip": ip, "device_type": "router", "network_id": swicth_obj.network_id, "l3_network_uuid": l3_network_uuid}
                    
            action_param["create_vpc_router"] = {
                "name": router_name,
                "router_offering_uuid":  current_app.config["ZSTACK_CLOUD"]["router_uuid"],
                "resource_uuid": op_route
            }
            action_param["update_router_state"] = {
                "op_route": op_route,
                "state": 1
            }
            create_transaction(action_param, range_uuid)
            
        except Exception:
            db.session.rollback()
            print(traceback.print_exc())
            abort(400, message="创建交换机失败")
        return {"code": 200, "data": {"router_id": op_route}}

    def delete(self):
        parser = reqparse.RequestParser()
        parser.add_argument("range_uuid", help="靶场信息有误", required=True)
        parser.add_argument("router_id", help="路由器信息有误", required=True)
        args = parser.parse_args()

        range_uuid = args["range_uuid"]
        router_id = args["router_id"]
        self.delete_route(range_uuid, router_id)
        return {"code": 200, "data": "删除成功"}

    def get(self, router_id):
        # 查询路由器是否已经创建, 没创建则返回为空
        if  not switchModel.Routes.query.filter_by(op_route=router_id).all():
            return {"code": 200, "data": {"gw_ip": []}}
        gw_ip_list = switchModel.Ports.query.filter_by(device_id=router_id).all()
        router_gw = [port.ip for port in gw_ip_list]
        return {"code": 200, "data": {"gw_ip": router_gw}}


class SwitchCidr(VmBase):
    def get(self, range_uuid):
        switch_list = switchModel.Switchs.query.filter_by(range_uuid=range_uuid).all()
        cidr_list = flask_config.NETWORK["CIDRS"]
        cidr_list = copy.copy(cidr_list)
        for switch in switch_list:
            if switch.cidr:
                index = cidr_list.index(switch.cidr)
                cidr_list.pop(index)
        return {"code": 200, "data": cidr_list}


class RouterTables(VmBase):
    def post(self):
        parser = reqparse.RequestParser()
        parser.add_argument("router_id", help="路由器信息有误", required=True)
        parser.add_argument("destination_cidr", help="目的cidr信息有误", required=True)
        parser.add_argument("next_hop", help="下一跳信息有误", required=True)
        args = parser.parse_args()

        router = switchModel.Routes.query.filter_by(op_route=args["router_id"]).first()
        next_hop = False
        switch_cidr_list = []

        if router:
            switch_str = router.binding_switchs
            if switch_str:
                switch_list = switch_str.split(",")
                for switch_id in switch_list:
                    switch = switchModel.Switchs.query.filter_by(
                        id=int(switch_id)
                    ).first()
                    if switch:
                        switch_cidr_list.append(switch.cidr)

        if "/" in args["next_hop"]:
            abort(400, message="输入的下一跳不合法")
        next_hop_list = args['next_hop'].split('.')
        if len(next_hop_list) != 4 or not next_hop_list[-1]:
            abort(400, message="输入的下一跳不合法")
        if '/' not in args["destination_cidr"]:
            abort(400, message="输入的目的cidr不合法")

        try:
            IPy.IP(args["destination_cidr"])
        except ValueError:
            abort(400, message="输入的目的cidr不合法")
        try:
            for cidr in switch_cidr_list:
                if args["next_hop"] in IPy.IP(cidr):
                    next_hop = True
                    break
        except ValueError:
            abort(400, message="输入的下一跳不合法")
        if not next_hop:
            abort(400, message="输入的下一跳不在该路由器连接的子网所在的网段内")
        router_table = switchModel.RouterTable.query.filter_by(destination_cidr=args["destination_cidr"], router_id=args['router_id']).first()
        if router_table:
            abort(400, message="输入的目的cidr已存在")

        routes = []
        router_table_list = switchModel.RouterTable.query.filter_by(
            router_id=router.op_route
        ).all()
        for route_table in router_table_list:
            static_route = {
                "destination": route_table.destination_cidr,
                "nexthop": route_table.next_hop,
            }
            routes.append(static_route)

        routes.append(
            {"destination": args["destination_cidr"], "nexthop": args["next_hop"]}
        )
        try:
            self.conn.update_router(router.op_route, routes=routes)
        except exceptions.BadRequestException as e:
            current_app.logger.error("静态路由表创建失败", exc_info=e)
            abort(400, message="静态路由表创建失败: 输入的目的cidr或吓一跳不合法")
        obj = switchModel.RouterTable(
            router_id=args["router_id"],
            destination_cidr=args["destination_cidr"],
            next_hop=args["next_hop"],
        )
        db.session.add(obj)
        db.session.commit()
        return {"code": 200, "data": {"route_table_id": obj.id}}

    def get(self, router_id):
        routes = []
        router_table_list = switchModel.RouterTable.query.filter_by(
            router_id=router_id
        ).all()
        for route_table in router_table_list:
            static_route = {
                "id": route_table.id,
                "destination": route_table.destination_cidr,
                "nexthop": route_table.next_hop,
            }
            routes.append(static_route)

        return {"code": 200, "data": routes}

    def delete(self):
        parser = reqparse.RequestParser()
        parser.add_argument("route_table_id", help="路由器信息有误", required=True)
        parser.add_argument("router_id", help="路由器信息有误", required=True)
        args = parser.parse_args()

        router_table_ob = switchModel.RouterTable.query.filter_by(
            id=args["route_table_id"]
        ).first()
        if not router_table_ob:
            abort(400, message="该路由表不存在")
        routes = []
        router_table_list = switchModel.RouterTable.query.filter_by(
            router_id=args["router_id"]
        ).all()
        for route_table in router_table_list:
            static_route = {
                "destination": route_table.destination_cidr,
                "nexthop": route_table.next_hop,
            }
            routes.append(static_route)
        index = routes.index(
            {
                "destination": router_table_ob.destination_cidr,
                "nexthop": router_table_ob.next_hop,
            }
        )

        routes.pop(index)
        self.conn.update_router(args["router_id"], routes=routes)

        db.session.delete(router_table_ob)
        db.session.commit()
        return {"code": 200, "info": "删除成功"}
